package com.yuqih.common.service.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import com.yuqih.common.config.IFrameworkConfig;
import com.yuqih.common.dao.IBaseDao;
import com.yuqih.common.dao.ICommonDao;
import com.yuqih.common.dto.FieldSpecifiedUtils;
import com.yuqih.common.dto.IFieldSpecified;
import com.yuqih.common.dto.Page;
import com.yuqih.common.dto.PageMetadata;
import com.yuqih.common.dto.TypeDescriptor;
import com.yuqih.common.service.ICommonService;
import com.yuqih.common.service.IConvertBuilder;
import com.yuqih.common.service.IDirItemService;
import com.yuqih.common.service.IQueryModelCreator;
import com.yuqih.common.service.IServiceRegistry;

public class CommonService implements ICommonService, IServiceRegistry {

	private ICommonDao commonDao;

	private IFrameworkConfig frameworkConfig;

	private IDirItemService dirItemService;

	private ServiceRegistry modelRegistry = new ServiceRegistry();

	@Override
	public <T> void register(Class<T> type, IQueryModelCreator<T> creator) {
		modelRegistry.register(type, creator);
	}

	@Override
	public <S, R> void register(Class<S> sourceType, Class<R> targetType, IConvertBuilder<S, R> builder) {
		modelRegistry.register(sourceType, targetType, builder);
	}	

	@Transactional
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public <T> void save(Class<T> type, T model) {

		if (model == null) {
			throw new NullPointerException();
		}

		Class entityType = frameworkConfig.getEntityType(type);

		Object entity = FieldSpecifiedUtils.createProxyInstance(entityType);
		IFieldSpecified fieldSpecified = (IFieldSpecified)entity;
				
		IConvertBuilder modelToEntityBuilder = modelRegistry.getConvertBuilder(type, entityType);
		modelToEntityBuilder.build(model, entity);
		
		IBaseDao baseDao = frameworkConfig.getBaseDao(type);
		
		fieldSpecified.setMonitorChangedFieldNames(true);
		int effected;
		if (baseDao != null) {
			effected = baseDao.insert(entity);
		} else {
			effected = commonDao.insert(entityType, entity);
		}
		Assert.isTrue(effected == 1);

		// 回写到模型
		IConvertBuilder entityToModelBuilder = modelRegistry.getConvertBuilder(entityType, type);
		entityToModelBuilder.build(entity, model, fieldSpecified.getChangedFieldNames());
	}
	
	@Transactional
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public <T> void save(Class<T> type, List<T> models) {
		Class entityType = frameworkConfig.getEntityType(type);
		IBaseDao baseDao = frameworkConfig.getBaseDao(type);
		List entities = new ArrayList();
		for (T model : models) {
			Object entity = FieldSpecifiedUtils.createProxyInstance(entityType);
			IFieldSpecified fieldSpecified = (IFieldSpecified)entity;
			
			IConvertBuilder modelToEntityBuilder = modelRegistry.getConvertBuilder(type, entityType);
			modelToEntityBuilder.build(model, entity);
			
			fieldSpecified.setMonitorChangedFieldNames(true);
			entities.add(entity);
		}
		
		int effected;
		if (baseDao != null) {
			effected = baseDao.insertBatch(entities);
		} else {
			effected = commonDao.insertBatch(entityType, entities);
		}

		Assert.isTrue(effected == models.size());

		IConvertBuilder entityToModelBuilder = modelRegistry.getConvertBuilder(entityType, type);
		
		for (int i = 0; i < models.size(); i++) {
			IFieldSpecified entity = (IFieldSpecified)entities.get(i);
			T model = models.get(i);
			entityToModelBuilder.build(entity, model, entity.getChangedFieldNames());
		}
		
	}

	@Transactional
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
	public <T> void modify(Class<T> type, T model) {
		if (model == null) {
			throw new NullPointerException();
		}

		Class entityType = frameworkConfig.getEntityType(type);

		Object entity = FieldSpecifiedUtils.createProxyInstance(entityType);
		IFieldSpecified fieldSpecified = (IFieldSpecified)entity;
		
		IConvertBuilder modelToEntityBuilder = modelRegistry.getConvertBuilder(type, entityType);
		modelToEntityBuilder.build(model, entity);

		IBaseDao baseDao = frameworkConfig.getBaseDao(type);
		
		fieldSpecified.setMonitorChangedFieldNames(true);
		int effected;
		if (baseDao != null) {
			effected = baseDao.update(entity);
		} else {
			effected = commonDao.update(entityType, entity);
		}

		Assert.isTrue(effected == 1);

		IConvertBuilder entityToModelBuilder = modelRegistry.getConvertBuilder(entityType, type);
		entityToModelBuilder.build(entity, model, fieldSpecified.getChangedFieldNames());
	}
	
	@Transactional
	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public <T> void modify(Class<T> type, List<T> models) {
		Class entityType = frameworkConfig.getEntityType(type);
		IBaseDao baseDao = frameworkConfig.getBaseDao(type);
		List entities = new ArrayList();
		for (T model : models) {
			Object entity = FieldSpecifiedUtils.createProxyInstance(entityType);
			IFieldSpecified fieldSpecified = (IFieldSpecified)entity;
			
			IConvertBuilder modelToEntityBuilder = modelRegistry.getConvertBuilder(type, entityType);
			modelToEntityBuilder.build(model, entity);
			
			fieldSpecified.setMonitorChangedFieldNames(true);
			entities.add(entity);
		}
		
		@SuppressWarnings("unused")
		int effected;
		if (baseDao != null) {
			effected = baseDao.updateBatch(entities);
		} else {
			effected = commonDao.updateBatch(entityType, entities);
		}

		// 这里返回值为-1
		// Assert.isTrue(effected == entities.size());

		IConvertBuilder entityToModelBuilder = modelRegistry.getConvertBuilder(entityType, type);
		
		for (int i = 0; i < models.size(); i++) {
			IFieldSpecified entity = (IFieldSpecified)entities.get(i);
			T model = models.get(i);
			entityToModelBuilder.build(entity, model, entity.getChangedFieldNames());
		}
		
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Transactional
	@Override
	public <T> void remove(Class<T> type, Object id) {
		if (id == null) {
			throw new IllegalArgumentException();
		}

		Class<?> entityType = frameworkConfig.getEntityType(type);

		IBaseDao baseDao = frameworkConfig.getBaseDao(type);
		int effected;
		if (baseDao != null) {
			effected = baseDao.delete(id);
		} else {
			effected = commonDao.delete(entityType, id);
		}

		Assert.isTrue(effected == 1);
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Transactional
	@Override
	public <T> void remove(Class<T> type, List<?> ids) {
		Class<?> entityType = frameworkConfig.getEntityType(type);

		IBaseDao baseDao = frameworkConfig.getBaseDao(type);
		int effected;
		if (baseDao != null) {
			effected = baseDao.deleteBatch(ids);
		} else {
			effected = commonDao.deleteBatch(entityType, ids);
		}

		Assert.isTrue(effected == ids.size());
	}

	@SuppressWarnings("unchecked")
	private <T> T createQueryModelInstance(Class<T> type) {
		Enhancer enhancer = new Enhancer();
		enhancer.setClassLoader(type.getClassLoader());

		enhancer.setSuperclass(type);
		enhancer.setCallback(new Interceptor(frameworkConfig.getTypeDescriptor(type)));

		T instance = (T) enhancer.create();
		IQueryModelCreator<T> creator = (IQueryModelCreator<T>) modelRegistry.getQueryModelCreator(type);
		if (creator != null) {
			return creator.create(instance);
		}
		return instance;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Transactional
	@Override
	public <T> T findById(Class<T> type, Object id) {
		if (id == null) {
			throw new IllegalArgumentException();
		}

		Class<?> entityType = frameworkConfig.getEntityType(type);

		IBaseDao baseDao = frameworkConfig.getBaseDao(type);
		Object entity;

		if (baseDao != null) {
			entity = baseDao.selectById(id);
		} else {
			entity = commonDao.selectById(entityType, id);
		}

		if (entity == null) {
			return null;
		}

		IConvertBuilder entityToModelBuilder = modelRegistry.getConvertBuilder(entityType, type);
		T model = createQueryModelInstance(type);
		entityToModelBuilder.build(entity, model);
		return model;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Transactional
	@Override
	public <T> List<T> list(Class<T> type, Object condition) {
		Class<?> entityType = frameworkConfig.getEntityType(type);

		IBaseDao baseDao = frameworkConfig.getBaseDao(type);
		List<?> entities;
		if (baseDao != null) {
			entities = baseDao.selectList(condition);
		} else {
			entities = commonDao.selectList(entityType, condition, null);
		}

		List<T> models = new ArrayList<T>();
		IConvertBuilder entityToModelBuilder = modelRegistry.getConvertBuilder(entityType, type);
		for (Object entity : entities) {
			T model = createQueryModelInstance(type);
			entityToModelBuilder.build(entity, model);
			models.add(model);
		}

		return models;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Transactional
	@Override
	public <T> Page<T> page(Class<T> type, Object condition, PageMetadata pageMetadata) {
		Class<?> entityType = frameworkConfig.getEntityType(type);

		IBaseDao baseDao = frameworkConfig.getBaseDao(type);
		int count;
		List<?> entities;
		if (baseDao != null) {
			entities = baseDao.selectList(condition, pageMetadata);
			count = baseDao.count(condition);
		} else {
			entities = commonDao.selectList(entityType, condition, pageMetadata);
			count = commonDao.count(entityType, condition);
		}

		List<T> models = new ArrayList<T>();
		IConvertBuilder entityToModelBuilder = modelRegistry.getConvertBuilder(entityType, type);
		for (Object entity : entities) {
			T model = createQueryModelInstance(type);
			entityToModelBuilder.build(entity, model);
			models.add(model);
		}

		Page<T> page = new Page<T>();
		page.setPageNo(pageMetadata.getPageNo());
		page.setPageSize(pageMetadata.getPageSize());
		page.setTotal(count);
		page.setRecords(models);

		return page;
	}

	/**
	 * @param commonDao
	 *            the commonDao to set
	 */
	public void setCommonDao(ICommonDao commonDao) {
		this.commonDao = commonDao;
	}

	/**
	 * @param frameworkConfig
	 *            the frameworkConfig to set
	 */
	public void setFrameworkConfig(IFrameworkConfig frameworkConfig) {
		this.frameworkConfig = frameworkConfig;
	}

	/**
	 * @param dirItemService
	 *            the dirItemService to set
	 */
	public void setDirItemService(IDirItemService dirItemService) {
		this.dirItemService = dirItemService;
	}

	private class Interceptor extends QueryModelInterceptor {

		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		Interceptor(TypeDescriptor modelDescriptor) {
			super(modelDescriptor);
		}

		@Override
		IDirItemService getDirItemService() {
			return dirItemService;
		}

		@Override
		IFrameworkConfig getFrameworkConfig() {
			return frameworkConfig;
		}

		@Override
		ICommonService getCommonService() {
			return CommonService.this;
		}

	}

	private class ServiceRegistry implements IServiceRegistry {

		private final Map<Class<?>, IQueryModelCreator<?>> queryModelCreatorMapping = new HashMap<Class<?>, IQueryModelCreator<?>>();
		private final Map<TypePair, IConvertBuilder<?, ?>> convertBuilderMapping = new HashMap<TypePair, IConvertBuilder<?, ?>>();

		
		@Override
		public <T> void register(Class<T> type, IQueryModelCreator<T> creator) {
			queryModelCreatorMapping.put(type, creator);
		}
		
		@Override
		public <S, R> void register(Class<S> sourceType, Class<R> targetType, IConvertBuilder<S, R> builder) {
			convertBuilderMapping.put(new TypePair(sourceType, targetType), builder);
		}		

		@SuppressWarnings("rawtypes")
		IQueryModelCreator getQueryModelCreator(Class<?> modelType) {
			return queryModelCreatorMapping.get(modelType);
		}

		@SuppressWarnings("rawtypes")
		IConvertBuilder getConvertBuilder(Class<?> sourceType, Class<?> targetType) {
			TypePair typePair = new TypePair(sourceType, targetType);
			IConvertBuilder convertBuilder = convertBuilderMapping.get(typePair);
			if (convertBuilder != null) {
				return convertBuilder;
			}
			
			synchronized (convertBuilderMapping) {
				convertBuilder = convertBuilderMapping.get(typePair);
				if (convertBuilder != null) {
					return convertBuilder;
				}
				
				convertBuilder = new DefaultConvertBuilder(sourceType, targetType, frameworkConfig);
				convertBuilderMapping.put(typePair, convertBuilder);
				return convertBuilder;
			}
		}
		
		

	}

	private static class TypePair {

		private final Class<?> modelType;
		private final Class<?> entityType;

		public TypePair(Class<?> modelType, Class<?> entityType) {
			super();
			this.modelType = modelType;
			this.entityType = entityType;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#hashCode()
		 */
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((entityType == null) ? 0 : entityType.hashCode());
			result = prime * result + ((modelType == null) ? 0 : modelType.hashCode());
			return result;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#equals(java.lang.Object)
		 */
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			TypePair other = (TypePair) obj;
			if (entityType == null) {
				if (other.entityType != null)
					return false;
			} else if (!entityType.equals(other.entityType))
				return false;
			if (modelType == null) {
				if (other.modelType != null)
					return false;
			} else if (!modelType.equals(other.modelType))
				return false;
			return true;
		}

	}

	

	

	

	

}
